<html><head><meta charset="utf-8"/><title>集成API</title></head><body><h1>集成API</h1><div class="x-wiki-content x-main-content">
 <p>
  在上一节中，我们用Vue实现了一个简单的TODO应用。通过对Model的更新，DOM结构可以同步更新。
 </p>
 <p>
  现在，如果要把这个简单的TODO应用变成一个用户能使用的Web应用，我们需要解决几个问题：
 </p>
 <ol>
  <li>
   用户的TODO数据应该从后台读取；
  </li>
  <li>
   对TODO的增删改必须同步到服务器后端；
  </li>
  <li>
   用户在View上必须能够修改TODO。
  </li>
 </ol>
 <p>
  第1个和第2个问题都是和API相关的。只要我们实现了合适的API接口，就可以在MVVM内部更新Model的同时，通过API把数据更新反映到服务器端，这样，用户数据就保存到了服务器端，下次打开页面时就可以读取TODO列表。
 </p>
 <p>
  我们在
  <code>
   vue-todo
  </code>
  的基础上创建
  <code>
   vue-todo-2
  </code>
  项目，结构如下：
 </p>
 <pre><code>vue-todo-2/
|
+- .vscode/
|  |
|  +- launch.json &lt;-- VSCode 配置文件
|
+- app.js &lt;-- koa app
|
+- static-files.js &lt;-- 支持静态文件的koa middleware
|
+- controller.js &lt;-- 支持路由的koa middleware
|
+- rest.js &lt;-- 支持REST的koa middleware
|
+- package.json &lt;-- 项目描述文件
|
+- node_modules/ &lt;-- npm安装的所有依赖包
|
+- controllers/ &lt;-- 存放Controller
|  |
|  +- api.js &lt;-- REST API
|
+- static/ &lt;-- 存放静态资源文件
   |
   +- css/ &lt;-- 存放bootstrap.css等
   |
   +- fonts/ &lt;-- 存放字体文件
   |
   +- js/ &lt;-- 存放各种js文件
   |
   +- index.html &lt;-- 使用MVVM的静态页面
</code></pre>
 <p>
  在
  <code>
   api.js
  </code>
  文件中，我们用数组在服务器端模拟一个数据库，然后实现以下4个API：
 </p>
 <ul>
  <li>
   GET /api/todos：返回所有TODO列表；
  </li>
  <li>
   POST /api/todos：创建一个新的TODO，并返回创建后的对象；
  </li>
  <li>
   PUT /api/todos/:id：更新一个TODO，并返回更新后的对象；
  </li>
  <li>
   DELETE /api/todos/:id：删除一个TODO。
  </li>
 </ul>
 <p>
  和上一节的TODO数据结构相比，我们需要增加一个
  <code>
   id
  </code>
  属性，来唯一标识一个TODO。
 </p>
 <p>
  准备好API后，在Vue中，我们如何把Model的更新同步到服务器端？
 </p>
 <p>
  有两个方法，一是直接用jQuery的AJAX调用REST API，不过这种方式比较麻烦。
 </p>
 <p>
  第二个方法是使用
  <a href="https://github.com/vuejs/vue-resource">
   vue-resource
  </a>
  这个针对Vue的扩展，它可以给VM对象加上一个
  <code>
   $resource
  </code>
  属性，通过
  <code>
   $resource
  </code>
  来方便地操作API。
 </p>
 <p>
  使用vue-resource只需要在导入vue.js后，加一行
  <code>
   &lt;script&gt;
  </code>
  导入
  <code>
   vue-resource.min.js
  </code>
  文件即可。可以直接使用CDN的地址：
 </p>
 <pre><code>&lt;script src="https://cdn.jsdelivr.net/vue.resource/1.0.3/vue-resource.min.js"&gt;&lt;/script&gt;
</code></pre>
 <p>
  我们给VM增加一个
  <code>
   init()
  </code>
  方法，读取TODO列表：
 </p>
 <pre><code>var vm = new Vue({
    el: '#vm',
    data: {
        title: 'TODO List',
        todos: []
    },
    created: function () {
        this.init();
    },
    methods: {
        init: function () {
            var that = this;
            that.$resource('/api/todos').get().then(function (resp) {
                // 调用API成功时调用json()异步返回结果:
                resp.json().then(function (result) {
                    // 更新VM的todos:
                    that.todos = result.todos;
                });
            }, function (resp) {
                // 调用API失败:
                alert('error');
            });
        }
    }
});
</code></pre>
 <p>
  注意到创建VM时，
  <code>
   created
  </code>
  指定了当VM初始化成功后的回调函数，这样，
  <code>
   init()
  </code>
  方法会被自动调用。
 </p>
 <p>
  类似的，对于添加、修改、删除的操作，我们也需要往VM中添加对应的函数。以添加为例：
 </p>
 <pre><code>var vm = new Vue({
    ...
    methods: {
        ...
        create: function (todo) {
            var that = this;
            that.$resource('/api/todos').save(todo).then(function (resp) {
                resp.json().then(function (result) {
                    that.todos.push(result);
                });
            }, showError);
        },
        update: function (todo, prop, e) {
            ...
        },
        remove: function (todo) {
            ...
        }
    }
});
</code></pre>
 <p>
  添加操作需要一个额外的表单，我们可以创建另一个VM对象
  <code>
   vmAdd
  </code>
  来对表单作双向绑定，然后，在提交表单的事件中调用
  <code>
   vm
  </code>
  对象的
  <code>
   create
  </code>
  方法：
 </p>
 <pre><code>var vmAdd = new Vue({
    el: '#vmAdd',
    data: {
        name: '',
        description: ''
    },
    methods: {
        submit: function () {
            vm.create(this.$data);
        }
    }
});
</code></pre>
 <p>
  <code>
   vmAdd
  </code>
  和FORM表单绑定：
 </p>
 <pre><code>&lt;form id="vmAdd" action="#0" v-on:submit.prevent="submit"&gt;
    &lt;p&gt;&lt;input type="text" v-model="name"&gt;&lt;/p&gt;
    &lt;p&gt;&lt;input type="text" v-model="description"&gt;&lt;/p&gt;
    &lt;p&gt;&lt;button type="submit"&gt;Add&lt;/button&gt;&lt;/p&gt;
&lt;/form&gt;
</code></pre>
 <p>
  最后，把
  <code>
   vm
  </code>
  绑定到对应的DOM：
 </p>
 <pre><code>&lt;div id="vm"&gt;
    &lt;h3&gt;{{ title }}&lt;/h3&gt;
    &lt;ol&gt;
        &lt;li v-for="t in todos"&gt;
            &lt;dl&gt;
                &lt;dt contenteditable="true" v-on:blur="update(t, 'name', $event)"&gt;{{ t.name }}&lt;/dt&gt;
                &lt;dd contenteditable="true" v-on:blur="update(t, 'description', $event)"&gt;{{ t.description }}&lt;/dd&gt;
                &lt;dd&gt;&lt;a href="#0" v-on:click="remove(t)"&gt;Delete&lt;/a&gt;&lt;/dd&gt;
            &lt;/dl&gt;
        &lt;/li&gt;
    &lt;/ol&gt;
&lt;/div&gt;
</code></pre>
 <p>
  这里我们用
  <code>
   contenteditable="true"
  </code>
  让DOM节点变成可编辑的，用
  <code>
   v-on:blur="update(t, 'name', $event)"
  </code>
  在编辑结束时调用
  <code>
   update()
  </code>
  方法并传入参数，特殊变量
  <code>
   $event
  </code>
  表示DOM事件本身。
 </p>
 <p>
  删除TODO是通过对
  <code>
   &lt;a&gt;
  </code>
  节点绑定
  <code>
   v-on:click
  </code>
  事件并调用
  <code>
   remove()
  </code>
  方法实现的。
 </p>
 <p>
  如果一切无误，我们就可以先启动服务器，然后在浏览器中访问
  <code>
   http://localhost:3000/static/index.html
  </code>
  ，对TODO进行增删改等操作，操作结果会保存在服务器端。
 </p>
 <p>
  通过Vue和vue-resource插件，我们用简单的几十行代码就实现了一个真正可用的TODO应用。
 </p>
 <p>
  使用CSS修饰后的页面效果如下：
 </p>
 <p>
  <img alt="mvvm-todo-2" data-src="/files/attachments/1109555870169152/l" src="https://www.liaoxuefeng.com/files/attachments/1109555870169152/l"/>
 </p>
 <h3>
  参考源码
 </h3>
 <p>
  <a href="https://github.com/michaelliao/learn-javascript/tree/master/samples/node/web/vue/vue-todo-2">
   vue-todo-2
  </a>
 </p>
</div>
</body></html>